#pragma once

#include "math.h"
#include "MinkowskiDifference2.h"

class MPRObserver
{
public:
	virtual ~MPRObserver() {}
	virtual void OnPhase1Started(const MinkowskiDifference2& difference, const Vector2& v0) {}
	virtual void OnPortalFound(const MinkowskiDifference2::SupportPoint& v1Ex, const MinkowskiDifference2::SupportPoint& v2Ex) {}
	virtual void OnPortalRefined(const MinkowskiDifference2::SupportPoint& v1Ex, const MinkowskiDifference2::SupportPoint& v2Ex) {}

	static MPRObserver& nullObject();
};

/** @brief implementation of Minkowski Portal Refinment(MPR) */
class MPRKernel2
{
public:
	/* set-up */
	MPRKernel2(const MinkowskiDifference2& difference, Scalar boundaryTolerance);
	void setObserver(MPRObserver& observer) { observer_ = &observer; }

public:
	/* state query */

	/** if a portal is found or not. */
	static const unsigned int HAS_PORTAL_BIT		= 0x1 << 1;		
	/** if algorithm has this already detected the origin is inside of the difference or not. */
	static const unsigned int DETECT_BIT			= 0x1 << 2;
	/** if the origin is inside or not. 
	 *  @invariant it is always set with DETECT_BIT */
	static const unsigned int HIT_BIT				= 0x1 << 3;
	/** if a gap between the outside edge of the portal and the boundary is thin enough.
	 *  @invariant it is always set with DETECT_BIT */
	static const unsigned int REACH_BOUNDARY_BIT	= 0x1 << 4;

	bool doesMeet(unsigned int flags) const;

	/* behavour */
public:
	/* batch interface */
	/** @attention algorithm ignore HIT_BIT because it depends not on progress but on geometry. */
	void run(unsigned int terminateFlags);
	void run(const Vector2& v0, unsigned int terminateFlags);

private:
	/* step-by-step */
	bool findNewPortal(const Vector2& v0, unsigned int terminateFlags);
	bool refinePortal(unsigned int terminateFlags);


public:
	/* compute contact from current state */
	bool getContactPoint(Vector2& point, Vector2& pointOnA, Vector2& pointOnB, Vector2& normalA) const;


private:
	/* get current portal. */
	const Vector2& v0() const {	return v0_;	}
	const Vector2 v1() const {	return v1Ex_.point();	}
	const Vector2 v2() const {	return v2Ex_.point();	}
	//const Vector2 n() const {	return n_;	}	//not always normalized!
	const MinkowskiDifference2::SupportPoint& v1Ex() const {	return v1Ex_;	}
	const MinkowskiDifference2::SupportPoint& v2Ex() const {	return v2Ex_;	}

private:
	// portal manipulation
	static const Vector2 leftNormal(const Vector2& startPoint, const Vector2& endPoint);
	static const Vector2 rightNormal(const Vector2& startPoint, const Vector2& endPoint);
	static bool isOriginOutside(const Vector2& point, const Vector2& normal);
	bool doOriginRayIntersectPortal() const;
	const Vector2 findInsideRay() const;

private:
	unsigned int state_;
	const MinkowskiDifference2* difference_;
	Scalar boundaryTolerance_;
	MPRObserver* observer_;

	// current portal
	Vector2								v0_;
	MinkowskiDifference2::SupportPoint  v1Ex_;
	MinkowskiDifference2::SupportPoint  v2Ex_;
	//Vector2								n_;
};
